/*
 * OpenParts
 * A dynamic-loading components framework for GWT
 * Copyright (C) 2011 Christophe Bouthier  [chris{AT}binary-gastronome{DOT}fr]
 *
 * This work is partially based on work I have done at INRIA (http://www.inria.fr) 
 * in the context of the Qualipso European Project (http://qualipso.org/),
 * The work done at INRIA is Copyright (C) 2006-2011 INRIA
 *
 * This work is distributed under the LGPL version 3
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version 3
 * as published by the Free Software Foundation. See the GNU
 * Lesser General Public License in LGPL.txt for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package fr.openparts.OpenParts.test.gwt.tests;

import com.google.gwt.dom.client.Element;
import com.google.gwt.junit.client.GWTTestCase;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.RootPanel;

import fr.openparts.OpenParts.client.OPBinding;
import fr.openparts.OpenParts.client.OPCommand;
import fr.openparts.OpenParts.client.OPCommandCallback;
import fr.openparts.OpenParts.client.OPCommandFactory;
import fr.openparts.OpenParts.client.OPCommandFailureReason;
import fr.openparts.OpenParts.client.OPParams;
import fr.openparts.OpenParts.client.OPPartsLoader;
import fr.openparts.OpenParts.client.OPPartsLoaderBinding;
import fr.openparts.OpenParts.client.OPScheduler;
import fr.openparts.OpenParts.client.OPSchedulerBinding;
import fr.openparts.OpenParts.client.OPShell;
import fr.openparts.OpenParts.test.gwt.dummyPart.client.DummyPart;

/**
 * GWT tests for OPScheduler.
 * 
 * @author Christophe Bouthier [chris{AT}binary-gastronome{DOT}fr]
 * @creation.date 09 November 2010
 */
public class OPSchedulerTestGwt extends GWTTestCase {

    private static final String SERVICE = "dummyService";
    private static final String RESOURCE = "dummyResource";
    private static final String ACTION = "dummyAction1";
    private static final String ACTION_MANDATORY = "dummyAction2";
    private static final String SLOTID = "dummySlotId";
    private static final String MANDATORY_PARAM_1 = "dummyParam1";
    private static final String MANDATORY_PARAM_2 = "dummyParam2";
    private static final String EVENT = "dummyEvent";
    private static final String SERVICE_1 = "dummyService1";
    private static final String SERVICE_2 = "dummyService2";

    private static final String VIEW_1_CONTENT = "viewFactorySimple";
    private static final String VIEW_2_CONTENT = "viewFactoryMandatoryParams";

    private static final int TIMER_TIMEOUT = 5500;
    private static final int TEST_TIMEOUT = 6000;

    private OPSchedulerBinding scheduler;
    private OPPartsLoaderBinding loader;

    private OPCommand command;
    private OPCommandCallback callback;

    private DummyPart dummyPart;

    private Element slotElement;

    private int onSuccessCalled = 0;
    private int onFailCalled = 0;
    private OPCommandFailureReason onFailReason;

    private Timer timer;
    private Timer timer2;

    /**
     * Return the name of the tested GWT Module.
     * 
     * @see com.google.gwt.junit.client.GWTTestCase#getModuleName()
     */
    @Override
    public final String getModuleName() {
        return "fr.openparts.OpenParts.test.gwt.TestOpenParts";
    }

    /**
     * Called at the start of each test to set up the part.
     * 
     * @see com.google.gwt.junit.client.GWTTestCase#gwtSetUp()
     */
    @Override
    protected void gwtSetUp() throws Exception {
        super.gwtSetUp();
        OPPartsLoader.bind();
        OPScheduler.bind();
        scheduler = getSchedulerBinding(OPShell.opBindingSharedInstance());
        loader = getPartsLoaderBinding(OPShell.opBindingSharedInstance());
        dummyPart = new DummyPart();
        dummyPart.bind();
        slotElement = OPShell.getSlot(SLOTID).getElement();
        callback = new OPCommandCallback() {
            @Override
            public void onSuccess() {
                ++onSuccessCalled;
            }

            @Override
            public void onFail(final OPCommandFailureReason reason) {
                ++onFailCalled;
                onFailReason = reason;
            }
        };
    }

    /**
     * Called at the end of each test to clean up the mess.
     * 
     * @see com.google.gwt.junit.client.GWTTestCase#gwtTearDown()
     */
    @Override
    protected final void gwtTearDown() throws Exception {
        super.gwtTearDown();
        command = null;
        onSuccessCalled = 0;
        onFailCalled = 0;
        onFailReason = null;
        RootPanel.getBodyElement().removeChild(slotElement);
        unbindOPScheduler(OPShell.opBindingSharedInstance());
        unbindOPPartsLoader(OPShell.opBindingSharedInstance());
        emptyListeners(OPShell.opBindingSharedInstance());
        if (timer != null) {
            timer.cancel();
            timer = null;
        }
        if (timer2 != null) {
            timer2.cancel();
            timer2 = null;
        }
    }

    /**
     * Test binding the scheduler. Should get something not null.
     */
    public final void testBinding() {
        assertNotNull("There should be a scheduler now", scheduler);
        assertTrue("There should be a schedule command method", isThereScheduleCommandMethod(scheduler));
    }

    /**
     * Test scheduling a null command. Should raise an assertion error.
     */
    public final void testScheduleNullCommand() {
        if (assertionsAreEnabled()) {
            try {
                scheduler.scheduleCommand(null);
                fail("OPCommandFactory should raise an assertion error when trying to schedule a null command.");
            } catch (AssertionError e) {
                assertTrue("Error should be an AssertionError", e instanceof AssertionError);
            }
        }
    }

    /**
     * Test scheduling a load part module command. Should find the corresponding script element in the page.
     */
    public final void testScheduleLoadPartCommand() {
        command = OPCommandFactory.loadPartCommandFactory(SERVICE, null);

        scheduler.scheduleCommand(command);
        loader.notifyLoadingCompleted(SERVICE);

        final Element scriptElement = DOM.getElementById(OPShell.partLoaderIdForService(SERVICE));
        assertNotNull("There should be a script in the page", scriptElement);
    }

    /**
     * Test scheduling a load part command with callback. Should call back the callback onSuccess.
     */
    public final void testScheduleLoadPartCommandWithCallback() {
        command = OPCommandFactory.loadPartCommandFactory(SERVICE, callback);

        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onFail should not have been called yet", 0, onFailCalled);

        scheduler.scheduleCommand(command);
        loader.notifyLoadingCompleted(SERVICE);

        assertEquals("onSuccess should have been called now", 1, onSuccessCalled);
        assertEquals("onFail should still not have been called", 0, onFailCalled);
    }

    /**
     * Test scheduling a load part command that will fail. Should call back the callback onFailure, giving a timeout reason.
     */
    public final void testScheduleLoadPartCommandThatFail() {
        command = OPCommandFactory.loadPartCommandFactory(SERVICE, callback);

        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onFail should not have been called yet", 0, onFailCalled);

        scheduler.scheduleCommand(command);

        timer = new Timer() {
            public void run() {
                assertEquals("onSuccess should still not have been called", 0, onSuccessCalled);
                assertEquals("onFail should have been called now", 1, onFailCalled);
                assertNotNull("reason shouldn't be null", onFailReason);
                assertEquals("reason should be timeout", OPCommandFailureReason.TYPE_LOAD_TIMEOUT, onFailReason.getType());
                assertNotNull("reason params shouldn't be null", onFailReason.getParams());
                assertEquals("reason service should be " + SERVICE, SERVICE, onFailReason.getParams().getParamValue(OPCommandFailureReason.PARAM_SERVICE));
                finishTest();
            }
        };

        timer.schedule(TIMER_TIMEOUT);
        delayTestFinish(TEST_TIMEOUT);
    }

    /**
     * Test scheduling a display view command. Should find the corresponding view in the page.
     */
    public final void testScheduleDisplayViewCommand() {
        RootPanel.getBodyElement().appendChild(slotElement);

        command = OPCommandFactory.displayViewCommandFactory(SERVICE, RESOURCE, ACTION, null, SLOTID);
        scheduler.scheduleCommand(command);
        loader.notifyLoadingCompleted(SERVICE);

        final String viewAsHtml = RootPanel.getBodyElement().getInnerHTML();
        assertNotNull("view as html shouldn't be null", viewAsHtml);
        assertTrue("There should be the view in the page", viewAsHtml.contains(VIEW_1_CONTENT));
    }

    /**
     * Test scheduling a display view command with callback. Should call back the callback onSuccess.
     */
    public final void testScheduleDisplayViewCommandWithCallback() {
        RootPanel.getBodyElement().appendChild(slotElement);

        command = OPCommandFactory.displayViewCommandFactory(SERVICE, RESOURCE, ACTION, null, SLOTID);
        command.setCallback(callback);

        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onFail should not have been called yet", 0, onFailCalled);

        scheduler.scheduleCommand(command);
        loader.notifyLoadingCompleted(SERVICE);

        assertEquals("onSuccess should have been called now", 1, onSuccessCalled);
        assertEquals("onFail should still not have been called", 0, onFailCalled);
    }

    /**
     * Test scheduling a display view command without slot id.
     */
    public final void testScheduleDisplayViewCommandWithoutSlotId() {
        command = OPCommandFactory.displayViewCommandFactory(SERVICE, RESOURCE, ACTION, null, SLOTID);
        command.setCallback(callback);

        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onFail should not have been called yet", 0, onFailCalled);

        scheduler.scheduleCommand(command);
        loader.notifyLoadingCompleted(SERVICE);

        assertEquals("onSuccess should still not have been called", 0, onSuccessCalled);
        assertEquals("onFail should have been called now", 1, onFailCalled);
        assertNotNull("reason shouldn't be null", onFailReason);
        assertEquals("reason should be no such slot", OPCommandFailureReason.TYPE_NO_SUCH_SLOT, onFailReason.getType());
        assertNotNull("reason params shouldn't be null", onFailReason.getParams());
        assertEquals("reason slot id should be " + SLOTID, SLOTID, onFailReason.getParams().getParamValue(OPCommandFailureReason.PARAM_SLOT_ID));
    }

    /**
     * Test scheduling a display view command with service already loaded. Should call back the callback onSuccess.
     */
    public final void testScheduleDisplayViewCommandWithServiceAlreadyLoaded() {
        RootPanel.getBodyElement().appendChild(slotElement);

        command = OPCommandFactory.loadPartCommandFactory(SERVICE, null);
        final OPCommand command2 = OPCommandFactory.displayViewCommandFactory(SERVICE, RESOURCE, ACTION, null, SLOTID);
        command2.setCallback(callback);

        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onFail should not have been called yet", 0, onFailCalled);

        scheduler.scheduleCommand(command);
        loader.notifyLoadingCompleted(SERVICE);
        scheduler.scheduleCommand(command2);

        assertEquals("onSuccess should have been called now", 1, onSuccessCalled);
        assertEquals("onFail should still not have been called", 0, onFailCalled);
    }

    /**
     * Test scheduling a display view command with service loaded just after. Should call back the callback onSuccess.
     */
    public final void testScheduleDisplayViewCommandWithServiceLoadedJustAfter() {
        RootPanel.getBodyElement().appendChild(slotElement);

        command = OPCommandFactory.loadPartCommandFactory(SERVICE, null);
        final OPCommand command2 = OPCommandFactory.displayViewCommandFactory(SERVICE, RESOURCE, ACTION, null, SLOTID);
        command2.setCallback(callback);

        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onFail should not have been called yet", 0, onFailCalled);

        scheduler.scheduleCommand(command);
        scheduler.scheduleCommand(command2);
        loader.notifyLoadingCompleted(SERVICE);

        assertEquals("onSuccess should have been called now", 1, onSuccessCalled);
        assertEquals("onFail should still not have been called", 0, onFailCalled);
    }

    /**
     * Test scheduling a display view command with a failed service loading. Should call back the callback onFail with a pre command failed reason.
     */
    public final void testScheduleDisplayViewCommandWithFailingServiceLoading() {
        RootPanel.getBodyElement().appendChild(slotElement);

        command = OPCommandFactory.displayViewCommandFactory(SERVICE, RESOURCE, ACTION, null, SLOTID);
        command.setCallback(callback);

        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onFail should not have been called yet", 0, onFailCalled);

        scheduler.scheduleCommand(command);

        timer = new Timer() {
            public void run() {
                assertEquals("onSuccess should still not have been called", 0, onSuccessCalled);
                assertEquals("onFail should have been called now", 1, onFailCalled);
                assertNotNull("reason shouldn't be null", onFailReason);
                assertEquals("reason should be pre command failed", OPCommandFailureReason.TYPE_PRE_COMMAND_FAILED, onFailReason.getType());
                assertNotNull("reason params shouldn't be null", onFailReason.getParams());
                assertEquals("reason service should be " + SERVICE, SERVICE, onFailReason.getParams().getParamValue(OPCommandFailureReason.PARAM_SERVICE));
                finishTest();
            }
        };

        timer.schedule(TIMER_TIMEOUT);
        delayTestFinish(TEST_TIMEOUT);
    }

    /**
     * Test scheduling a display view command for a non-binded view. Should call back the callback onFail with a no such view reason.
     */
    public final void testSceduleDisplayViewCommandForNotBindedView() {
        RootPanel.getBodyElement().appendChild(slotElement);

        command = OPCommandFactory.displayViewCommandFactory(SERVICE, RESOURCE, ACTION + "_invalid", null, SLOTID);
        command.setCallback(callback);

        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onFail should not have been called yet", 0, onFailCalled);

        scheduler.scheduleCommand(command);
        loader.notifyLoadingCompleted(SERVICE);

        assertEquals("onSuccess should still not have been called", 0, onSuccessCalled);
        assertEquals("onFail should have been called now", 1, onFailCalled);
        assertNotNull("reason shouldn't be null", onFailReason);
        assertEquals("reason should be no such view", OPCommandFailureReason.TYPE_NO_SUCH_VIEW, onFailReason.getType());
        assertNotNull("reason params shouldn't be null", onFailReason.getParams());
        assertEquals("reason service should be " + SERVICE, SERVICE, onFailReason.getParams().getParamValue(OPCommandFailureReason.PARAM_SERVICE));
        assertEquals("reason resource should be " + RESOURCE, RESOURCE, onFailReason.getParams().getParamValue(OPCommandFailureReason.PARAM_RESOURCE));
        assertEquals("reason action should be " + ACTION + "_invalid", ACTION + "_invalid",
                onFailReason.getParams().getParamValue(OPCommandFailureReason.PARAM_ACTION));
    }

    /**
     * Test scheduling a display view command with mandatory params. Should find the corresponding view in the page.
     */
    public final void testScheduleDisplayViewCommandWithMandatoryParams() {
        RootPanel.getBodyElement().appendChild(slotElement);

        command = OPCommandFactory.displayViewCommandFactory(SERVICE, RESOURCE, ACTION_MANDATORY, new OPParams(MANDATORY_PARAM_1, "value1", MANDATORY_PARAM_2,
                "value2"), SLOTID);
        command.setCallback(callback);

        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onFail should not have been called yet", 0, onFailCalled);

        scheduler.scheduleCommand(command);
        loader.notifyLoadingCompleted(SERVICE);

        assertEquals("onSuccess should have been called now", 1, onSuccessCalled);
        assertEquals("onFail should still not have been called", 0, onFailCalled);

        final String viewAsHtml = RootPanel.getBodyElement().getInnerHTML();
        assertNotNull("view as html shouldn't be null", viewAsHtml);
        assertTrue("view 2 content is in the page", viewAsHtml.contains(VIEW_2_CONTENT));
    }

    /**
     * Test scheduling a display view command with missing mandatory param. Should call back the callback onFail with a missing mandatory param reason.
     */
    public final void testScheduleDisplayViewCommandWithMissingMandatoryParam() {
        RootPanel.getBodyElement().appendChild(slotElement);

        command = OPCommandFactory.displayViewCommandFactory(SERVICE, RESOURCE, ACTION_MANDATORY, null, SLOTID);
        command.setCallback(callback);

        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onFail should not have been called yet", 0, onFailCalled);

        scheduler.scheduleCommand(command);
        loader.notifyLoadingCompleted(SERVICE);

        assertEquals("onSuccess should still not have been called", 0, onSuccessCalled);
        assertEquals("onFail should have been called now", 1, onFailCalled);
        assertNotNull("reason shouldn't be null", onFailReason);
        assertEquals("reason should be missing mandatory param", OPCommandFailureReason.TYPE_MISSING_MANDATORY_PARAMS, onFailReason.getType());
        assertNotNull("reason params shouldn't be null", onFailReason.getParams());
        assertEquals("reason missing param should be " + MANDATORY_PARAM_1, MANDATORY_PARAM_1,
                onFailReason.getParams().getParamValue(OPCommandFailureReason.PARAM_MISSING_PARAM));
    }

    /**
     * Test scheduling a display view command with another missing mandatory param. Should call back the callback onFail with a missing mandatory param reason.
     */
    public final void testScheduleDisplayViewCommandWithAnotherMissingMandatoryParam() {
        RootPanel.getBodyElement().appendChild(slotElement);

        command = OPCommandFactory.displayViewCommandFactory(SERVICE, RESOURCE, ACTION_MANDATORY, new OPParams(MANDATORY_PARAM_1, "value1"), SLOTID);
        command.setCallback(callback);

        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onFail should not have been called yet", 0, onFailCalled);

        scheduler.scheduleCommand(command);
        loader.notifyLoadingCompleted(SERVICE);

        assertEquals("onSuccess should still not have been called", 0, onSuccessCalled);
        assertEquals("onFail should have been called now", 1, onFailCalled);
        assertNotNull("reason shouldn't be null", onFailReason);
        assertEquals("reason should be missing mandatory param", OPCommandFailureReason.TYPE_MISSING_MANDATORY_PARAMS, onFailReason.getType());
        assertNotNull("reason params shouldn't be null", onFailReason.getParams());
        assertEquals("reason missing param should be " + MANDATORY_PARAM_2, MANDATORY_PARAM_2,
                onFailReason.getParams().getParamValue(OPCommandFailureReason.PARAM_MISSING_PARAM));
    }

    /**
     * Test scheduling a dispatch event command. Should get the event.
     */
    public final void testScheduleDispatchEventCommand() {
        command = OPCommandFactory.dispatchEventCommandFactory(EVENT, null);

        assertEquals("Listener should have been called 0 times.", 0, dummyPart.eventHandlerCalls);

        scheduler.scheduleCommand(command);

        assertEquals("Listener should have been called 1 times.", 1, dummyPart.eventHandlerCalls);
    }

    /**
     * Test scheduling a dispatch event command with callback. Should call back the callback onSuccess.
     */
    public final void testScheduleDispatchEventCommandWithCallback() {
        command = OPCommandFactory.dispatchEventCommandFactory(EVENT, null);
        command.setCallback(callback);

        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onFail should not have been called yet", 0, onFailCalled);

        scheduler.scheduleCommand(command);

        assertEquals("onSuccess should have been called now", 1, onSuccessCalled);
        assertEquals("onFail should still not have been called", 0, onFailCalled);
    }

    /**
     * Test scheduling a dispatch event command with parameters. Should call back the callback onSuccess.
     */
    public final void testScheduleDispatchEventCommandWithParameters() {
        command = OPCommandFactory.dispatchEventCommandFactory(EVENT, new OPParams("dummy_key", "dummy_value"));
        command.setCallback(callback);

        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onFail should not have been called yet", 0, onFailCalled);

        scheduler.scheduleCommand(command);

        assertEquals("onSuccess should have been called now", 1, onSuccessCalled);
        assertEquals("onFail should still not have been called", 0, onFailCalled);
    }

    /**
     * Test scheduling a dispatch event to command. Should get the event.
     */
    public final void testScheduleDispatchEventToCommand() {
        command = OPCommandFactory.dispatchEventToCommandFactory(SERVICE, EVENT, null);

        assertEquals("Listener should have been called 0 times.", 0, dummyPart.eventHandlerCalls);

        scheduler.scheduleCommand(command);
        loader.notifyLoadingCompleted(SERVICE);

        assertEquals("Listener should have been called 1 times.", 1, dummyPart.eventHandlerCalls);
    }

    /**
     * Test scheduling a dispatch event to command with callback. Should call back the callback onSuccess.
     */
    public final void testScheduleDispatchEventToCommandWithCallback() {
        command = OPCommandFactory.dispatchEventToCommandFactory(SERVICE, EVENT, null);
        command.setCallback(callback);

        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onFail should not have been called yet", 0, onFailCalled);

        scheduler.scheduleCommand(command);
        loader.notifyLoadingCompleted(SERVICE);

        assertEquals("onSuccess should have been called now", 1, onSuccessCalled);
        assertEquals("onFail should still not have been called", 0, onFailCalled);
    }

    /**
     * Test scheduling a dispatch event to command with parameters. Should call back the callback onSuccess.
     */
    public final void testScheduleDispatchEventToCommandWithParameters() {
        command = OPCommandFactory.dispatchEventToCommandFactory(SERVICE, EVENT, new OPParams("dummy_key", "dummy_value"));
        command.setCallback(callback);

        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onFail should not have been called yet", 0, onFailCalled);

        scheduler.scheduleCommand(command);
        loader.notifyLoadingCompleted(SERVICE);

        assertEquals("onSuccess should have been called now", 1, onSuccessCalled);
        assertEquals("onFail should still not have been called", 0, onFailCalled);
    }

    /**
     * Test scheduling a dispatch event to command with service already loaded. Should call back the callback onSuccess.
     */
    public final void testScheduleDispatchEventToCommandWithServiceAlreadyLoaded() {
        command = OPCommandFactory.loadPartCommandFactory(SERVICE, null);

        final OPCommand command2 = OPCommandFactory.dispatchEventToCommandFactory(SERVICE, EVENT, null);
        command2.setCallback(callback);

        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onFail should not have been called yet", 0, onFailCalled);

        scheduler.scheduleCommand(command);
        loader.notifyLoadingCompleted(SERVICE);
        scheduler.scheduleCommand(command2);

        assertEquals("onSuccess should have been called now", 1, onSuccessCalled);
        assertEquals("onFail should still not have been called", 0, onFailCalled);
    }

    /**
     * Test scheduling a dispatch event to command with service loaded just after. Should call back the callback onSuccess.
     */
    public final void testScheduleDispatchEventToCommandWithServiceLoadedJustAfter() {
        command = OPCommandFactory.loadPartCommandFactory(SERVICE, null);

        final OPCommand command2 = OPCommandFactory.dispatchEventToCommandFactory(SERVICE, EVENT, null);
        command2.setCallback(callback);

        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onFail should not have been called yet", 0, onFailCalled);

        scheduler.scheduleCommand(command);
        scheduler.scheduleCommand(command2);
        loader.notifyLoadingCompleted(SERVICE);

        assertEquals("onSuccess should have been called now", 1, onSuccessCalled);
        assertEquals("onFail should still not have been called", 0, onFailCalled);
    }

    /**
     * Test scheduling a dispatch event to command with a failed service loading. Should call back the callback onFail with a pre command failed reason.
     */
    public final void testScheduleDispatchEventToCommandWithFailingServiceLoading() {
        command = OPCommandFactory.dispatchEventToCommandFactory(SERVICE, EVENT, null);
        command.setCallback(callback);

        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onFail should not have been called yet", 0, onFailCalled);

        scheduler.scheduleCommand(command);

        timer = new Timer() {
            public void run() {
                assertEquals("onSuccess should still not have been called", 0, onSuccessCalled);
                assertEquals("onFail should have been called now", 1, onFailCalled);
                assertNotNull("reason shouldn't be null", onFailReason);
                assertEquals("reason should be pre command failed", OPCommandFailureReason.TYPE_PRE_COMMAND_FAILED, onFailReason.getType());
                assertNotNull("reason params shouldn't be null", onFailReason.getParams());
                assertEquals("reason service should be " + SERVICE, SERVICE, onFailReason.getParams().getParamValue(OPCommandFailureReason.PARAM_SERVICE));
                finishTest();
            }
        };

        timer.schedule(TIMER_TIMEOUT);
        delayTestFinish(TEST_TIMEOUT);
    }

    /**
     * Test scheduling a dispatch event to command on a not valid service. Should call back the callback onFail with a no such service reason.
     */
    public final void testScheduleDispatchEventToCommandWithNoSuchService() {
        command = OPCommandFactory.dispatchEventToCommandFactory(SERVICE + "_dummy", EVENT, null);
        command.setCallback(callback);

        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onFail should not have been called yet", 0, onFailCalled);

        scheduler.scheduleCommand(command);
        loader.notifyLoadingCompleted(SERVICE + "_dummy");

        assertEquals("onSuccess should still not have been called", 0, onSuccessCalled);
        assertEquals("onFail should have been called now", 1, onFailCalled);
        assertNotNull("reason shouldn't be null", onFailReason);
        assertEquals("reason should be no such service", OPCommandFailureReason.TYPE_NO_SUCH_SERVICE, onFailReason.getType());
        assertNotNull("reason params shouldn't be null", onFailReason.getParams());
        assertEquals("reason service should be " + SERVICE + "_dummy", SERVICE + "_dummy",
                onFailReason.getParams().getParamValue(OPCommandFailureReason.PARAM_SERVICE));
    }

    /**
     * Test scheduling two load parts commands that are successfull. Should call the callback onSuccess twice.
     */
    public final void testScheduleTwoLoadPartsCommandsSuccess() {
        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onFail should not have been called yet", 0, onFailCalled);

        final OPCommand command1 = OPCommandFactory.loadPartCommandFactory(SERVICE_1, callback);
        final OPCommand command2 = OPCommandFactory.loadPartCommandFactory(SERVICE_2, callback);

        scheduler.scheduleCommand(command1);
        scheduler.scheduleCommand(command2);
        loader.notifyLoadingCompleted(SERVICE_1);
        loader.notifyLoadingCompleted(SERVICE_2);

        assertEquals("onSuccess should have been called twice", 2, onSuccessCalled);
        assertEquals("onFail should still not have been called", 0, onFailCalled);
    }

    /**
     * Test scheduling two load parts commands that are timed out. Should call the callback onFail twice.
     */
    public final void testScheduleTwoLoadPartsCommandsTimeout() {
        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onFail should not have been called yet", 0, onFailCalled);

        final OPCommand command1 = OPCommandFactory.loadPartCommandFactory(SERVICE_1, callback);
        final OPCommand command2 = OPCommandFactory.loadPartCommandFactory(SERVICE_2, callback);

        scheduler.scheduleCommand(command1);
        scheduler.scheduleCommand(command2);

        timer = new Timer() {
            public void run() {
                assertEquals("onSuccess should still not have been called", 0, onSuccessCalled);
                assertEquals("onFail should have been called twice", 2, onFailCalled);
                finishTest();
            }
        };

        timer.schedule(TIMER_TIMEOUT * 2);
        delayTestFinish(TEST_TIMEOUT * 2);
    }

    /**
     * Test scheduling two load parts commands, first is successfull and second is timed out. Should call both callback once each.
     */
    public final void testScheduleTwoLoadPartsCommandsSuccessAndTimeout() {
        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onFail should not have been called yet", 0, onFailCalled);

        final OPCommand command1 = OPCommandFactory.loadPartCommandFactory(SERVICE_1, callback);
        final OPCommand command2 = OPCommandFactory.loadPartCommandFactory(SERVICE_2, callback);

        scheduler.scheduleCommand(command1);
        scheduler.scheduleCommand(command2);
        loader.notifyLoadingCompleted(SERVICE_1);

        timer = new Timer() {
            public void run() {
                assertEquals("onSuccess should have been called once", 1, onSuccessCalled);
                assertEquals("onFail should have been called once", 1, onFailCalled);
                finishTest();
            }
        };

        timer.schedule(TIMER_TIMEOUT);
        delayTestFinish(TEST_TIMEOUT);
    }

    /**
     * Test scheduling two load parts commands, with success in wrong order. Should call both callback once each.
     */
    public final void testScheduleTwoLoadPartsCommandsWrongOrder() {
        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onFail should not have been called yet", 0, onFailCalled);

        final OPCommand command1 = OPCommandFactory.loadPartCommandFactory(SERVICE_1, callback);
        final OPCommand command2 = OPCommandFactory.loadPartCommandFactory(SERVICE_2, callback);

        scheduler.scheduleCommand(command1);
        scheduler.scheduleCommand(command2);
        loader.notifyLoadingCompleted(SERVICE_2);
        loader.notifyLoadingCompleted(SERVICE_1);

        timer = new Timer() {
            public void run() {
                assertEquals("onSuccess should have been called once", 1, onSuccessCalled);
                assertEquals("onFail should have been called once", 1, onFailCalled);
                finishTest();
            }
        };

        timer.schedule(TIMER_TIMEOUT);
        delayTestFinish(TEST_TIMEOUT);
    }

    /**
     * Test scheduling two load parts commands, first is timed out and second is successfull. Should call both callback once each.
     */
    public final void testScheduleTwoLoadPartsCommandsTimeoutAndSuccess() {
        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onFail should not have been called yet", 0, onFailCalled);

        final OPCommand command1 = OPCommandFactory.loadPartCommandFactory(SERVICE_1, callback);
        final OPCommand command2 = OPCommandFactory.loadPartCommandFactory(SERVICE_2, callback);

        scheduler.scheduleCommand(command1);
        scheduler.scheduleCommand(command2);

        timer = new Timer() {
            public void run() {
                loader.notifyLoadingCompleted(SERVICE_2);
                assertEquals("onSuccess should have been called once", 1, onSuccessCalled);
                assertEquals("onFail should have been called once", 1, onFailCalled);
                finishTest();
            }
        };

        timer.schedule(TIMER_TIMEOUT);
        delayTestFinish(TEST_TIMEOUT);
    }

    /**
     * Test scheduling two load parts commands, first is timed out, but success before second is timed out. Should NOT get a call to onSuccess.
     */
    public final void testScheduleTwoLoadPartsCommandsTimeoutSuccessTimeout() {
        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onFail should not have been called yet", 0, onFailCalled);

        final OPCommand command1 = OPCommandFactory.loadPartCommandFactory(SERVICE_1, callback);
        final OPCommand command2 = OPCommandFactory.loadPartCommandFactory(SERVICE_2, callback);

        scheduler.scheduleCommand(command1);
        scheduler.scheduleCommand(command2);

        timer = new Timer() {
            public void run() {
                loader.notifyLoadingCompleted(SERVICE_1);
                assertEquals("onSuccess should not have been called", 0, onSuccessCalled);
                assertEquals("onFail should have been called once", 1, onFailCalled);
            }
        };

        timer2 = new Timer() {
            public void run() {
                assertEquals("onSuccess should not have been called", 0, onSuccessCalled);
                assertEquals("onFail should have been called twice", 2, onFailCalled);
                finishTest();
            }
        };

        timer.schedule(TIMER_TIMEOUT);
        timer2.schedule(TIMER_TIMEOUT * 2);
        delayTestFinish(TEST_TIMEOUT * 2);
    }

    /**
     * Test scheduling two load parts commands, first is timed out, but success after second is timed out. Should NOT get a call to onSuccess.
     */
    public final void testScheduleTwoLoadPartsCommandsTimeoutTimeoutSuccess() {
        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onFail should not have been called yet", 0, onFailCalled);

        final OPCommand command1 = OPCommandFactory.loadPartCommandFactory(SERVICE_1, callback);
        final OPCommand command2 = OPCommandFactory.loadPartCommandFactory(SERVICE_2, callback);

        scheduler.scheduleCommand(command1);
        scheduler.scheduleCommand(command2);

        timer = new Timer() {
            public void run() {
                assertEquals("onSuccess should not have been called", 0, onSuccessCalled);
                assertEquals("onFail should have been called once", 1, onFailCalled);
            }
        };

        timer2 = new Timer() {
            public void run() {
                loader.notifyLoadingCompleted(SERVICE_1);
                assertEquals("onSuccess should not have been called", 0, onSuccessCalled);
                assertEquals("onFail should have been called twice", 2, onFailCalled);
                finishTest();
            }
        };

        timer.schedule(TIMER_TIMEOUT);
        timer2.schedule(TIMER_TIMEOUT * 2);
        delayTestFinish(TEST_TIMEOUT * 2);
    }

    /**
     * Test scheduling two load parts commands, first is timed out, but success before second is successfull. Should call both callback once each.
     */
    public final void testScheduleTwoLoadPartsCommandsTimeoutSuccessSuccess() {
        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onFail should not have been called yet", 0, onFailCalled);

        final OPCommand command1 = OPCommandFactory.loadPartCommandFactory(SERVICE_1, callback);
        final OPCommand command2 = OPCommandFactory.loadPartCommandFactory(SERVICE_2, callback);

        scheduler.scheduleCommand(command1);
        scheduler.scheduleCommand(command2);

        timer = new Timer() {
            public void run() {
                loader.notifyLoadingCompleted(SERVICE_1);
                loader.notifyLoadingCompleted(SERVICE_2);
                assertEquals("onSuccess should have been called just once", 1, onSuccessCalled);
                assertEquals("onFail should have been called once", 1, onFailCalled);
                finishTest();
            }
        };

        timer.schedule(TIMER_TIMEOUT);
        delayTestFinish(TEST_TIMEOUT);
    }

    /**
     * Test scheduling two load parts commands, first is timed out, but success after second is successfull. Should call both callback once each.
     */
    public final void testScheduleTwoLoadPartsCommandsTimeoutSuccessSuccess2() {
        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onFail should not have been called yet", 0, onFailCalled);

        final OPCommand command1 = OPCommandFactory.loadPartCommandFactory(SERVICE_1, callback);
        final OPCommand command2 = OPCommandFactory.loadPartCommandFactory(SERVICE_2, callback);

        scheduler.scheduleCommand(command1);
        scheduler.scheduleCommand(command2);

        timer = new Timer() {
            public void run() {
                loader.notifyLoadingCompleted(SERVICE_2);
                loader.notifyLoadingCompleted(SERVICE_1);
                assertEquals("onSuccess should have been called just once", 1, onSuccessCalled);
                assertEquals("onFail should have been called once", 1, onFailCalled);
                finishTest();
            }
        };

        timer.schedule(TIMER_TIMEOUT);
        delayTestFinish(TEST_TIMEOUT);
    }

    /**
     * Test scheduling several commands that are successfull.
     */
    public final void testScheduleSeveralCommandsSuccessfull() {
        RootPanel.getBodyElement().appendChild(slotElement);

        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onFail should not have been called yet", 0, onFailCalled);

        final OPCommand command1 = OPCommandFactory.loadPartCommandFactory(SERVICE, callback);
        final OPCommand command2 = OPCommandFactory.displayViewCommandFactory(SERVICE, RESOURCE, ACTION, null, SLOTID);
        command2.setCallback(callback);
        final OPCommand command3 = OPCommandFactory.dispatchEventCommandFactory(EVENT, null);
        command3.setCallback(callback);
        final OPCommand command4 = OPCommandFactory.dispatchEventToCommandFactory(SERVICE, EVENT, null);
        command4.setCallback(callback);
        final OPCommand command5 = OPCommandFactory.loadPartCommandFactory(SERVICE_1, callback);

        scheduler.scheduleCommand(command1);
        scheduler.scheduleCommand(command2);
        scheduler.scheduleCommand(command3);
        scheduler.scheduleCommand(command4);
        scheduler.scheduleCommand(command5);

        loader.notifyLoadingCompleted(SERVICE);
        loader.notifyLoadingCompleted(SERVICE_1);

        assertEquals("onSuccess should have been called twice", 5, onSuccessCalled);
        assertEquals("onFail should still not have been called", 0, onFailCalled);
    }

    /**
     * Test scheduling several commands with a failure for loading.
     */
    public final void testScheduleSeveralCommandsOneFailure() {
        RootPanel.getBodyElement().appendChild(slotElement);

        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onFail should not have been called yet", 0, onFailCalled);

        final OPCommand command1 = OPCommandFactory.loadPartCommandFactory(SERVICE, callback);
        final OPCommand command2 = OPCommandFactory.displayViewCommandFactory(SERVICE, RESOURCE, ACTION, null, SLOTID);
        command2.setCallback(callback);
        final OPCommand command3 = OPCommandFactory.dispatchEventCommandFactory(EVENT, null);
        command3.setCallback(callback);
        final OPCommand command4 = OPCommandFactory.dispatchEventToCommandFactory(SERVICE, EVENT, null);
        command4.setCallback(callback);
        final OPCommand command5 = OPCommandFactory.loadPartCommandFactory(SERVICE_1, callback);

        scheduler.scheduleCommand(command1);
        scheduler.scheduleCommand(command2);
        scheduler.scheduleCommand(command3);
        scheduler.scheduleCommand(command4);
        scheduler.scheduleCommand(command5);

        timer = new Timer() {
            public void run() {
                assertEquals("onSuccess should have been called once", 1, onSuccessCalled);
                assertEquals("onFail should have been called three times", 3, onFailCalled);
                loader.notifyLoadingCompleted(SERVICE_1);
                assertEquals("onSuccess should have been called twice", 2, onSuccessCalled);
                assertEquals("onFail should have been called three times", 3, onFailCalled);
                finishTest();
            }
        };

        timer.schedule(TIMER_TIMEOUT);
        delayTestFinish(TEST_TIMEOUT);
    }

    /**
     * Get the parts loader binding.
     * 
     * @param binding
     *            the general binding.
     * @return the parts loader binding.
     */
    private final native OPPartsLoaderBinding getPartsLoaderBinding(final OPBinding opbinding) /*-{
        return opbinding.partsLoader;
    }-*/;

    /**
     * Get the scheduler binding.
     * 
     * @param binding
     *            the general binding.
     * @return the scheduler binding.
     */
    private final native OPSchedulerBinding getSchedulerBinding(final OPBinding opbinding) /*-{
        return opbinding.scheduler;
    }-*/;

    /**
     * Check if there is a schedule command method in the given scheduler binding
     * 
     * @param binding
     *            the scheduler binding
     * @return true if the method is here
     */
    private final native boolean isThereScheduleCommandMethod(final OPSchedulerBinding opsbinding) /*-{
        return (opsbinding.scheduleCommand != null);
    }-*/;

    /**
     * Unbind the OPScheduler.
     */
    private final native void unbindOPScheduler(final OPBinding opbinding) /*-{
        opbinding.scheduler = null;
    }-*/;

    /**
     * Unbind the OPPartsLoader.
     */
    private final native void unbindOPPartsLoader(final OPBinding binding) /*-{
        binding.partsLoader = null;
    }-*/;

    /**
     * Native helper method to clean up event bus listener.
     */
    private final native void emptyListeners(final OPBinding binding) /*-{
        binding.listeners = {};
    }-*/;

    /**
     * Check if assertions are enabled.
     * 
     * @return true if assertions are enabled, false otherwise
     */
    private final boolean assertionsAreEnabled() {
        try {
            assert false;
            return false;
        } catch (AssertionError e) {
            return true;
        }
    }

}
